package com.sluggard.security.core.filter;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.sluggard.common.model.res.Result;
import com.sluggard.common.utils.NetUtil;
import com.sluggard.common.utils.Reflections;
import com.sluggard.security.core.annotation.Olog;
import com.sluggard.security.core.manage.SecurityUserManage;
import com.sluggard.security.manage.mapper.OlogMapper;
import com.sluggard.security.manage.model.entity.OlogEntity;
import com.sluggard.security.manage.model.vo.SecurityUser;
import com.sluggard.security.properties.SecurityConfigProperties;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

/**
 * @author outenkou
 */
@Slf4j
@Aspect
@Component
public class OlogInterceptor {

    private volatile Map<String, String> moduleNameMapping;

    @Resource
    private HttpServletRequest request;

    @Resource
    private OlogMapper ologMapper;

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private SecurityConfigProperties securityProperties;

    @Around("execution(* com.sluggard..*Controller+.*(..))" +
            " && (@annotation(org.springframework.web.bind.annotation.RequestMapping)" +
            " || @annotation(org.springframework.web.bind.annotation.PostMapping)" +
            " || @annotation(org.springframework.web.bind.annotation.GetMapping)" +
            " || @annotation(com.sluggard.security.core.annotation.Olog))")
    public Object intercept(ProceedingJoinPoint pjp) throws Throwable {
        Object result;
        StopWatch clock = new StopWatch();
        clock.start();
        try {
            result = pjp.proceed();
        } finally {
            clock.stop();
        }
        if (isIgnoreClasse(pjp) || isIgnoreMethod(pjp)) {
            return result;
        }
        Object target = pjp.getTarget();
        String methodName = pjp.getSignature().getName();
        Method method = Reflections.getAccessibleMethodByName(target, methodName);
        if (method != null) {
            Olog om = method.getAnnotation(Olog.class);
            if (om != null && om.ignore()) {
                return result;
            }
            if (om == null) {
                Class<?> clazz = target.getClass();
                om = clazz.getAnnotation(Olog.class);
                if (om != null && om.ignore()) {
                    return result;
                }
            }
        }

        try {
            OlogEntity olog = new OlogEntity();
            olog.setExecuteTime(clock.getTime());
            handleOperater(olog, result);
            handleModule(request, target, olog, methodName);
            handleAction(target, methodName, olog);
            handleParameters(pjp, olog, target, methodName);
            handleResult(olog, result);
            if (request != null) {
                olog.setClientInfos(getClientInformations(request));
            }
            ologMapper.insert(olog);
        } catch (Exception e) {
            log.error("保存操作日志", e);
        }
        return result;
    }

    private boolean isIgnoreClasse(ProceedingJoinPoint pjp) {
        return securityProperties.getOlog().getIgnoreClasses().contains(pjp.getTarget().getClass().getName());
    }

    private boolean isIgnoreMethod(ProceedingJoinPoint pjp) {
        return isIgnoreParameter(StringUtils.join(securityProperties.getOlog().getIgnoreMethods(), ","), pjp.getSignature().getName());
    }

    private boolean isIgnoreParameter(String params, String parameterName) {
        String[] ignores = StringUtils.split(params, ",");
        for (String ignoreStr : ignores) {
            if (isLike(ignoreStr, parameterName)) {
                return true;
            }
        }
        return false;
    }

    private boolean isLike(String srcIncludeStar, String dest) {
        if ("*".equals(srcIncludeStar)) {
            return true;
        } else if (srcIncludeStar.startsWith("*")) {
            return dest.endsWith(srcIncludeStar.substring(1));
        } else if (srcIncludeStar.endsWith("*")) {
            return dest.startsWith(srcIncludeStar.substring(0, srcIncludeStar.length() - 1));
        } else {
            return srcIncludeStar.equalsIgnoreCase(dest);
        }
    }

    protected void handleOperater(OlogEntity olog, Object result) {
        // 操作员获取和设置
        try {
            SecurityUser userInfo = SecurityUserManage.getUserInfo();
            if (userInfo != null) {
                olog.setUid(userInfo.getId());
                olog.setOperateUser(userInfo.getUsername() + "[" + userInfo.getRealName() + "]");
            }
        } catch (Exception ignored) {
        }
    }

    protected void handleModule(HttpServletRequest request, Object target, OlogEntity olog, String methodName) {
        if (request != null) {
            String module = request.getRequestURI();
            olog.setModule(module);
            olog.setModuleName(module);
        }
        Tag tag = target.getClass().getAnnotation(Tag.class);
        if (tag != null && StringUtils.isNotBlank(tag.name())) {
            olog.setModuleName(tag.name());
        }
        Method method = Reflections.getAccessibleMethodByName(target, methodName);
        if (method != null) {
            Olog om = method.getAnnotation(Olog.class);
            if (om != null && StringUtils.isNotBlank(om.module())) {
                olog.setModuleName(om.module());
            }
        }
    }


    private String getMatchName(String module) {
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        String moduleName = moduleNameMapping.get(module);
        if (StringUtils.isBlank(moduleName)) {
            String modulePatten = StringUtils.substringBeforeLast(module, "/") + "/*";
            for (Map.Entry<String, String> entry : moduleNameMapping.entrySet()) {
                if (antPathMatcher.match(modulePatten, entry.getKey())) {
                    moduleName = entry.getValue();
                    break;
                }
            }
        }
        if (StringUtils.isBlank(moduleName)) {
            moduleName = module;
        }
        return moduleName;
    }

    protected void handleAction(Object target, String methodName, OlogEntity olog) {
        String actionName = getActionName(methodName);
        Method method = Reflections.getAccessibleMethodByName(target, methodName);
        if (method != null) {
            Operation operation = method.getAnnotation(Operation.class);
            if (operation != null && StringUtils.isNotBlank(operation.summary())) {
                actionName = operation.summary();
            }
            Olog om = method.getAnnotation(Olog.class);
            if (om != null && StringUtils.isNotBlank(om.action())) {
                actionName = om.action();
            }
        }

        olog.setAction(methodName);
        olog.setActionName(actionName);
    }

    private String getActionName(String action) {
        String actionName = securityProperties.getOlog().getMethodNames().get(action);
        return actionName != null ? actionName : action;
    }

    protected void handleParameters(ProceedingJoinPoint pjp, OlogEntity olog, Object target, String methodName) {
        Signature signature = pjp.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] parameterNames = methodSignature.getParameterNames();
        Object[] args = pjp.getArgs();

        String param = null;
        String ignoreParams = StringUtils.join(securityProperties.getOlog().getIgnoreParams(), ",");
        Method method = Reflections.getAccessibleMethodByName(target, methodName);
        if (method != null) {
            Olog om = method.getAnnotation(Olog.class);
            if (om != null) {
                param = om.params();
                ignoreParams = ignoreParams + "," + om.ignoreParams();
            }
        }

        Map<String, Object> confirmParameters = new LinkedHashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            if (!isIgnoreParameter(parameterNames[i]) && args[i] != null && StringUtils.isNotBlank(args[i].toString())) {
                try {
                    confirmParameters.put(parameterNames[i], getRequestParameters(objectMapper.convertValue(args[i], Map.class), param, ignoreParams));
                } catch (IllegalArgumentException e) {
                    confirmParameters.put(parameterNames[i], args[i]);
                }
            }
        }
        olog.setRequestParams(confirmParameters.toString());
    }

    private boolean isIgnoreParameter(String parameterName) {
        for (String ignoreStr : securityProperties.getOlog().getIgnoreParams()) {
            if (isLike(ignoreStr, parameterName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 设置自定义参数
     *
     * @param params       只拦截的参数
     * @param ignoreParams 忽略的参数
     * @return
     */
    private String getRequestParameters(Map<?, ?> requestParameters, String params, String ignoreParams) {
        Map<String, Object> confirmParameters = new TreeMap<>();
        for (Map.Entry<?, ?> entry : requestParameters.entrySet()) {
            String key = (String) entry.getKey();
            // 如果参数值为空，则不获取该参数
            if (entry.getValue() == null || StringUtils.isBlank(entry.getValue().toString())) {
                continue;
            }
            // 如果是忽略参数
            if (StringUtils.isNotBlank(ignoreParams) && isIgnoreParameter(ignoreParams, key)) {
                continue;
            }
            // 如果是已设置参数
            if (StringUtils.isNotBlank(params)) {
                if (isIgnoreParameter(params, key)) {
                    confirmParameters.put(key, entry.getValue());
                }
                continue;
            }
            confirmParameters.put(key, entry.getValue());
        }
        return confirmParameters.size() > 0 ? confirmParameters.toString() : "";
    }

    /**
     * 获取处理结果
     *
     * @param result
     */
    protected void handleResult(OlogEntity olog, Object result) {
        if (result == null) {
            return;
        }
        if (result instanceof Result r) {
            olog.setOperateResult((r.success() ? 1 : 2));
            olog.setOperateMessage(r.getMessage());
            return;
        }
        olog.setOperateResult(1);
        olog.setOperateMessage(result.toString());
    }

    private String getClientInformations(HttpServletRequest request) {
        String requestUserAgent = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgent.parseUserAgentString(requestUserAgent);
        OperatingSystem os = userAgent.getOperatingSystem();
        Browser browser = userAgent.getBrowser();
        return NetUtil.getIpAddress(request) + " - " + os.getName() + " - " + browser.getName() + "/" + browser.getBrowserType().getName();
    }
}
